﻿using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WCAD.Kean
{
    public class KochizeAll
    {
        // We generate 4 new entities for every old entity

        // (unless a complex entity such as a polyline)


        const int newEntsPerOldEnt = 4;


        [CommandMethod("KA")]

        public void KA()

        {

            Document doc =

              Application.DocumentManager.MdiActiveDocument;

            Database db = doc.Database;

            Editor ed = doc.Editor;


            // Acquire user input - whether to create the

            // new geometry to the left or the right...


            PromptKeywordOptions pko =

              new PromptKeywordOptions(

                "\nCreate fractal to side (Left/<Right>): "

              );

            pko.Keywords.Add("Left");

            pko.Keywords.Add("Right");


            PromptResult pr =

              ed.GetKeywords(pko);

            bool bLeft = false;


            if (pr.Status != PromptStatus.None &&

                pr.Status != PromptStatus.OK)

                return;


            if ((string)pr.StringResult == "Left")

                bLeft = true;


            // ... and the recursion depth for the command.


            PromptIntegerOptions pio =

              new PromptIntegerOptions(

                "\nEnter recursion level <1>: "

              );

            pio.AllowZero = false;

            pio.AllowNegative = false;

            pio.AllowNone = true;


            PromptIntegerResult pir =

              ed.GetInteger(pio);

            int recursionLevel = 1;


            if (pir.Status != PromptStatus.None &&

                pir.Status != PromptStatus.OK)

                return;


            if (pir.Status == PromptStatus.OK)

                recursionLevel = pir.Value;


            // Note: strictly speaking we're not recursing,

            // we're iterating, but the effect to the user

            // is the same.


            Transaction tr =

              doc.TransactionManager.StartTransaction();

            using (tr)

            {

                BlockTable bt =

                  (BlockTable)tr.GetObject(

                    db.BlockTableId,

                    OpenMode.ForRead

                  );

                using (bt)

                {

                    // No need to open the block table record

                    // for write, as we're just reading data

                    // for now


                    BlockTableRecord btr =

                      (BlockTableRecord)tr.GetObject(

                        bt[BlockTableRecord.ModelSpace],

                        OpenMode.ForRead

                      );

                    using (btr)

                    {

                        // List of changed entities

                        // (will contain complex entities, such as

                        // polylines"


                        ObjectIdCollection modified =

                          new ObjectIdCollection();


                        // List of entities to erase

                        // (will contain replaced entities)


                        ObjectIdCollection toErase =

                          new ObjectIdCollection();


                        // List of new entitites to add

                        // (will be processed recursively or

                        // assed to the open block table record)


                        List<Entity> newEntities =

                          new List<Entity>(

                            db.ApproxNumObjects * newEntsPerOldEnt

                          );


                        // Kochize each entity in the open block

                        // table record


                        foreach (ObjectId objId in btr)

                        {

                            Entity ent =

                              (Entity)tr.GetObject(

                                objId,

                                OpenMode.ForRead

                              );

                            Kochize(

                              ent,

                              modified,

                              toErase,

                              newEntities,

                              bLeft

                            );

                        }


                        // If we need to loop,

                        // work on the returned entities


                        while (--recursionLevel > 0)

                        {

                            // Create an output array


                            List<Entity> newerEntities =

                              new List<Entity>(

                                newEntities.Count * newEntsPerOldEnt

                              );


                            // Kochize all the modified (complex) entities


                            foreach (ObjectId objId in modified)

                            {

                                Entity ent =

                                  (Entity)tr.GetObject(

                                    objId,

                                    OpenMode.ForRead

                                  );

                                Kochize(

                                  ent,

                                  modified,

                                  toErase,

                                  newerEntities,

                                  bLeft

                                );

                            }


                            // Kochize all the non-db resident entities


                            foreach (Entity ent in newEntities)

                            {

                                Kochize(

                                  ent,

                                  modified,

                                  toErase,

                                  newerEntities,

                                  bLeft

                                );

                            }


                            // We now longer need the intermediate entities

                            // previously output for the level above,

                            // we replace them with the latest output


                            newEntities.Clear();

                            newEntities = newerEntities;

                        }


                        // Erase each of the replaced db-resident entities


                        foreach (ObjectId objId in toErase)

                        {

                            Entity ent =

                              (Entity)tr.GetObject(

                                objId,

                                OpenMode.ForWrite

                              );

                            ent.Erase();

                        }


                        // Add the new entities


                        btr.UpgradeOpen();

                        foreach (Entity ent in newEntities)

                        {

                            btr.AppendEntity(ent);

                            tr.AddNewlyCreatedDBObject(ent, true);

                            

                        }

                        tr.Commit();

                    }

                }

            }

        }


        // Dispatch function to call through to various per-type

        // functions


        private void Kochize(

          Entity ent,

          ObjectIdCollection modified,

          ObjectIdCollection toErase,

          List<Entity> toAdd,

          bool bLeft

        )

        {

            Line ln = ent as Line;

            if (ln != null)

            {

                Kochize(ln, modified, toErase, toAdd, bLeft);

                return;

            }

            Arc arc = ent as Arc;

            if (arc != null)

            {

                Kochize(arc, modified, toErase, toAdd, bLeft);

                return;

            }

        }


        // Create 4 new lines from a line passed in


        private void Kochize(

          Line ln,

          ObjectIdCollection modified,

          ObjectIdCollection toErase,

          List<Entity> toAdd,

          bool bLeft

        )

        {

            // Get general info about the line

            // and calculate the main 5 points


            Point3d pt1 = ln.StartPoint,

                    pt5 = ln.EndPoint;

            Vector3d vec1 = pt5 - pt1,

                    norm1 = vec1.GetNormal();

            double d_3 = vec1.Length / 3;

            Point3d pt2 = pt1 + (norm1 * d_3),

                    pt4 = pt1 + (2 * norm1 * d_3);

            Vector3d vec2 = pt4 - pt2;


            if (bLeft)

                vec2 =

                  vec2.RotateBy(

                    Math.PI / 3, new Vector3d(0, 0, 1)

                  );

            else

                vec2 =

                  vec2.RotateBy(

                    5 * Math.PI / 3, new Vector3d(0, 0, 1)

                  );

            Point3d pt3 = pt2 + vec2;


            // Mark the original to be erased


            if (ln.ObjectId != ObjectId.Null)

                toErase.Add(ln.ObjectId);


            // Create the first line


            Line ln1 = new Line(pt1, pt2);

            ln1.SetPropertiesFrom(ln);

            ln1.Thickness = ln.Thickness;

            toAdd.Add(ln1);


            // Create the second line


            Line ln2 = new Line(pt2, pt3);

            ln2.SetPropertiesFrom(ln);

            ln2.Thickness = ln.Thickness;

            toAdd.Add(ln2);


            // Create the third line


            Line ln3 = new Line(pt3, pt4);

            ln3.SetPropertiesFrom(ln);

            ln3.Thickness = ln.Thickness;

            toAdd.Add(ln3);


            // Create the fourth line


            Line ln4 = new Line(pt4, pt5);

            ln4.SetPropertiesFrom(ln);

            ln4.Thickness = ln.Thickness;

            toAdd.Add(ln4);

        }


        // Create 4 new arcs from an arc passed in


        private void Kochize(

          Arc arc,

          ObjectIdCollection modified,

          ObjectIdCollection toErase,

          List<Entity> toAdd,

          bool bLeft

        )

        {

            // Get general info about the arc

            // and calculate the main 5 points


            Point3d pt1 = arc.StartPoint,

                    pt5 = arc.EndPoint;

            double length = arc.GetDistAtPoint(pt5),

                  angle = arc.StartAngle;

            //bool bLocalLeft = false;

            Vector3d full = pt5 - pt1;

            //if (full.GetAngleTo(Vector3d.XAxis) > angle)

            //bLocalLeft = true;


            Point3d pt2 = arc.GetPointAtDist(length / 3),

                    pt4 = arc.GetPointAtDist(2 * length / 3);


            // Mark the original to be erased


            if (arc.ObjectId != ObjectId.Null)

                toErase.Add(arc.ObjectId);


            // Create the first arc


            Point3d mid = arc.GetPointAtDist(length / 6);

            CircularArc3d tmpArc = new CircularArc3d(pt1, mid, pt2);

            Arc arc1 = circArc2Arc(tmpArc);

            arc1.SetPropertiesFrom(arc);

            arc1.Thickness = arc.Thickness;

            toAdd.Add(arc1);


            // Create the second arc


            mid = arc.GetPointAtDist(length / 2);

            tmpArc.Set(pt2, mid, pt4);

            if (bLeft)

                tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt2);

            else

                tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt2);

            Arc arc2 = circArc2Arc(tmpArc);

            arc2.SetPropertiesFrom(arc);

            arc2.Thickness = arc.Thickness;

            toAdd.Add(arc2);


            // Create the third arc


            mid = arc.GetPointAtDist(length / 2);

            tmpArc.Set(pt2, mid, pt4);

            if (bLeft)

                tmpArc.RotateBy(5 * Math.PI / 3, Vector3d.ZAxis, pt4);

            else

                tmpArc.RotateBy(Math.PI / 3, Vector3d.ZAxis, pt4);

            Arc arc3 = circArc2Arc(tmpArc);

            arc3.SetPropertiesFrom(arc);

            arc3.Thickness = arc.Thickness;

            toAdd.Add(arc3);


            // Create the fourth arc


            mid = arc.GetPointAtDist(5 * length / 6);

            Arc arc4 =

              circArc2Arc(new CircularArc3d(pt4, mid, pt5));

            arc4.SetPropertiesFrom(arc);

            arc4.Thickness = arc.Thickness;

            toAdd.Add(arc4);

        }


        Arc circArc2Arc(CircularArc3d circArc)

        {

            double ang, start, end;

            ang =

              circArc.ReferenceVector.GetAngleTo(Vector3d.XAxis);

            ang =

              (circArc.ReferenceVector.Y < 0 ? -ang : ang);

            start = circArc.StartAngle + ang;

            end = circArc.EndAngle + ang;


            return (

              new Arc(

                circArc.Center,

                circArc.Normal,

                circArc.Radius,

                start,

                end

              )

            );

        }
    }
}
